# Author: Chris Moyer
import os
import os.path
import re
import urlparse
import StringIO
import traceback

import cgi
import webob
import wsgiref
import wsgiref.headers

import boto
import marajo
from marajo.exceptions import *
from marajo.utils import find_class
from marajo.appengine.ext.webapp import status, template, session
from marajo.appengine.api import users
import time
import mimetypes

import logging
log = logging.getLogger("marajo.handlers")

class Request(webob.Request):
    """We like to add in session support here. Due to how the system must be distributable,
    all session information must be stored in the database."""
    file_extension = "html"

    def __init__(self, environ):
        charset = 'ascii'
        if environ.get('CONTENT_TYPE', '').find('charset') == -1:
            charset = 'utf-8'

        webob.Request.__init__(self, environ, charset=charset,
            unicode_errors= 'ignore', decode_param_names=True)

    def get(self, argument_name, default_value='', allow_multiple=False):
        param_value = self.get_all(argument_name)
        if allow_multiple:
            return param_value
        else:
            if len(param_value) > 0:
                return param_value[0]
            else:
                return default_value

    def get_all(self, argument_name):
        if self.charset:
            argument_name = argument_name.encode(self.charset)

        try:
            param_value = self.params.getall(argument_name)
        except KeyError:
            return default_value

        for i in range(len(param_value)):
            if isinstance(param_value[i], cgi.FieldStorage):
                param_value[i] = param_value[i].value

        return param_value

    def formDict(self):
        vals = {}
        if self.GET:
            for k in self.GET:
                vals[k] = self.GET[k]
        if self.POST:
            for k in self.POST:
                vals[k] = self.POST[k]
        return vals


class Response(webob.Response):
    """
    Simple Adapter class for a WSGI response object
    to support GAE
    """

    def __init__(self, body="", **params):
        webob.Response.__init__(self, body=body, **params)

    def set_status(self, code, message=None):
        """
        Set the HTTP status of our response

        @param code: HTTP Status Code, should be one of the (appengine.webapp.status)s
        @type code: integer from status.HTTP_*
        """
        if not message:
            message = status.message[code]
        self.status = "%s %s" % (code, message)

    def clear(self):
        self.body = ""

    def wsgi_write(self, environ, start_response):
        return self.__call__(environ, start_response)


class RequestHandler(object):
    """
    Simple WSGI request handler.
    This class is based on the Google Appengine RequestHandler class
    """

    def __init__(self, config, request, response):
        self.config = config
        self.request = request
        self.response = response
        self._session = None
        self._user = None
        if self.config.get("static_bucket"):
            s3 = boto.connect_s3()
            self._static_bucket = s3.get_bucket(self.config.get("static_bucket"))
        else:
            self._static_bucket = False

        if self.request.headers.get("X-Forwarded-Host") != None:
            self.host_url = "https://%s" % self.request.headers.get("X-Forwarded-Host")
        else:
            self.host_url = "http://%s" % self.request.headers.get("Host")

    def get_session(self):
        """
        Get or create the session
        """
        if self._session == None:
            self.start_session()
        return self._session

    def get_user(self):
        if self._user == None:
            self.get_session()
        return self._user


    def start_session(self):
        # Set up the session
        sid = None
        if self.request.cookies.has_key("Marajo_Session_ID"):
            sid = self.request.cookies['Marajo_Session_ID']

        if sid is not None:
            try:
                self._session = session.Session.get_by_ids(sid)
            except:
                sid = None
            if not self._session:
                sid = None


        if sid is None:
            self._session = session.Session()
            self._session.put()
            sid = self._session.id
            log.info("Created Session: %s" % sid)
            self.response.set_cookie("Marajo_Session_ID", sid)

        # Set up the user
        self._user = users.get_current_user(self._session)


    session = property(get_session)
    user = property(get_user)


    def call(self):
        """
        Request handler, passes off to get, post...etc
        """
        start_time = time.time()
        try:
            # Before Filters
            for filter in  self.config.get("before_filters", []):
                log.debug("Calling Before-Filter %s" % filter)
                f = find_class(filter)
                f = f(self)
                f.call()

            log.debug("%s:%s" % (self.__class__.__name__, self.request.method))

            if self.request.method == "GET":
                self.get()
            elif self.request.method == "POST":
                self.post()
            elif self.request.method == "HEAD":
                self.head()
            elif self.request.method == "OPTIONS":
                self.options()
            elif self.request.method == "PUT":
                self.put()
            elif self.request.method == "DELETE":
                self.delete()
            elif self.request.method == "TRACE":
                self.trace()
            else:
                raise BadRequest(description="Unknown Method %s" % self.request.method)

            # After Filters
            for filter in  self.config.get("after_filters", []):
                log.debug("Calling After-Filter %s" % filter)
                f = find_class(filter)
                f = f(self)
                f.call()
        except HTTPRedirect, e:
            # Handle Redirection
            self.response.set_status(e.code)
            self.redirect(e.url)
        except Unauthorized, e:
            # Require authentication
            self.response.clear()
            self.response.set_status(e.code)
            self.response.headers.add("WWW-Authenticate", 'Basic realm="%s"' % marajo.config.get("application"))
        except HTTPException, e:
            self.error(e.code, e.description)
        except Exception, e:
            # If any exceptions were thrown, this was probably an 
            # Internal Server Error
            self.error(500, str(e))
            log.critical(traceback.format_exc())

        end_time = time.time()
        log.info("Page Rendered in: %s seconds" % (int(end_time - start_time)))

        return self.response

    def error(self, code, details=None):
        """
        Clear out any content and return an error page
        """
        self.response.clear()
        self.response.set_status(code)
        if details is None:
            details = status.description[code]
        log.error("[%s] %s %s" % (code, self.request.url, status.message[code]))
        template_values = {
            "error_code": code,
            "error_message": status.message[code],
            "error_description": details,
        }
        if marajo.config.get("debug", False):
            tb_str = traceback.format_exc().strip()
            if tb_str and tb_str != "None":
                template_values["stack_trace"] = tb_str
        self.display('error.tmpl', template_values)

    def redirect(self, url, perminate=False):
        """
        HTTP Redirect
        @param perminate: Send a 301 (Perminate Redirect) instead of a 303 (Temporary Redirect) Default False
        @type perminate: boolean
        """
        self.response.clear()
        if perminate:
            self.response.set_status(301)
        else:
            self.response.set_status(303)
        self.response.headers['Location'] = str(url)

    def display(self, template_name, values={}):
        """
        Display using the template "template", passing arguments
        "values" plus the following information:
        version = marajo.config.get("version")

        If the user is logged in:
        user = {"id": user.id, "auth_groups": user.get_auth_groups(), "username": user.username}
        logout_url = users.create_logout_url("/")

        If the user is NOT logged in:
        login_url = users.create_login_url(self.request.url)

        @param template_name: Template name to display
        @type template_name: string

        @param values: Dictionary of values to send to the template
        @type values: dict
        """
        values['version'] = str(marajo.config.get('version', "1"))
        values['static_file'] = self._static_file
        values['host_url'] = self.host_url
        values['script_name'] = self.request.script_name
        if self.user:
            user = {
                "id": self.user.id,
                "auth_groups": self.user.get_auth_groups(),
                "username": self.user.username,
                "email": self.user.email,
                "nickname": self.user.nickname,
                "oids": self.user.oids,
            }
            for group in self.user.get_auth_groups():
                user["is_%s" % group]= True
            values['user'] = user
            values['logout_url'] = users.create_logout_url("/")
        else:
            values['login_url'] = users.create_login_url(self.request.path_info)
        template_dirs = template.template_dirs[:]
        # Get the specific template dir for this user agent if
        # provided
        log.debug("User Agent: %s" % self.request.user_agent)
        for user_agent in marajo.config.get("templates", []):
            match = re.match("^.*%s.*$" %user_agent['user_agent'], self.request.user_agent)
            if match:
                template_dirs.insert(0,user_agent['template_dir'])
        if self.config.get("template_dir"):
            template_dirs.insert(0, os.path.abspath(self.config.get("template_dir")))

        template_dirs_tmp = template_dirs[:]
        template_dirs_tmp.reverse()
        for dir in template_dirs_tmp:
            template_dirs.insert(0, os.path.join(dir, self.request.file_extension))
            mtype =  mimetypes.guess_type(".%s" % self.request.file_extension)[0]
            if mtype:
                self.response.content_type = mtype
        self.response.write(template.render(template_name, values, template_dirs=template_dirs))

    def get(self):
        self.any()

    def post(self):
        self.any()
    
    def head(self):
        self.any()

    def options(self):
        self.any()

    def put(self):
        self.any()

    def delete(self):
        self.any()

    def trace(self):
        self.any()

    def any(self):
        """
        Default handler for any request not specifically defined
        """
        self.error(status.HTTP_METHOD_NOT_ALLOWED)

    def _static_file(self, file_name):
        """
        Template function to get the static file locaction for this file
        """
        if self.config.get("static_domain"):
            return "%s%s" % (self.config.get_static_domain, file_name)
        elif self._static_bucket:
            key = self._static_bucket.get_key(file_name.strip("/"))
            if key:
                return key.generate_url(0, query_auth=False, force_http=True)
        return file_name
